package com.ruge.tool.http;

import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.cookie.BasicCookieStore;
import org.apache.hc.client5.http.cookie.CookieStore;
import org.apache.hc.client5.http.entity.UrlEncodedFormEntity;
import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.socket.ConnectionSocketFactory;
import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.NameValuePair;
import org.apache.hc.core5.http.config.Registry;
import org.apache.hc.core5.http.config.RegistryBuilder;
import org.apache.hc.core5.http.io.SocketConfig;
import org.apache.hc.core5.http.message.BasicHeader;
import org.apache.hc.core5.util.TimeValue;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * description: HttpConfig
 * create time at 2022/11/25 16:12
 *
 * @author alice.ruge
 * @since 0.0.3
 */

public class HttpConfig {
    /**
     * 定义HttpClient构造器
     */
    private final HttpClientBuilder builder = HttpClientBuilder.create();
    /**
     * 定义头信息
     */
    private Map<String, String> headerMap;
    /**
     * 定义CookieStore对象
     */
    private CookieStore cookieStore;
    /**
     * 定义Basic Auth管理对象
     */
    private BasicCredentialsProvider basicCredentialsProvider;
    /**
     * 定义请求参数
     */
    private List<NameValuePair> pairs;
    /**
     * 定义默认请求类型
     */
    private static String contentType = "application/json";
    /**
     * 定义请求和响应字符编码
     */
    private Charset reqCode = StandardCharsets.UTF_8;
    private Charset resCode = StandardCharsets.UTF_8;

    public HttpConfig() {
        //设置连接基本配置
        //注册访问协议相关的Socket工厂
        Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", PlainConnectionSocketFactory.INSTANCE)
                .register("https", trustHttpsCertificates())
                .build();
        //设置连接池管理器
        PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager(registry);
        manager.setMaxTotal(100);
        manager.setDefaultMaxPerRoute(manager.getMaxTotal() / 2);
        manager.setValidateAfterInactivity(TimeValue.ofMinutes(5));
        manager.setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(30, TimeUnit.SECONDS).setTcpNoDelay(true).build());
        //为HttpClientBuilder设置连接池
        builder.setConnectionManager(manager);
        //设置定期清理连接池中过期的连接
        builder.evictExpiredConnections();
        builder.evictIdleConnections(TimeValue.ofMinutes(3));
        //设置请求基础配置
        //创建默认CookieStore
        cookieStore = new BasicCookieStore();
        //创建默认Basic Auth对象
        basicCredentialsProvider = new BasicCredentialsProvider();
        //设置Http请求基本参数
        RequestConfig requestConfig = RequestConfig.custom()
                // 设置启用重定向
                .setRedirectsEnabled(true)
                // 设置最大重定向次数
                .setMaxRedirects(30)
                // 设置连接超时时间
                .setConnectTimeout(2, TimeUnit.MINUTES)
                // 设置请求超时时间
                .setConnectionRequestTimeout(2, TimeUnit.MINUTES)
                // 设置响应超时时间
                .setResponseTimeout(2, TimeUnit.MINUTES)
                .build();
        //为HttpClientBuilder设置连接配置
        builder.setDefaultRequestConfig(requestConfig);
        //为HttpClientBuilder设置头信息
        builder.setDefaultHeaders(buildHeader());
        builder.setDefaultCookieStore(cookieStore);
        builder.setDefaultCredentialsProvider(basicCredentialsProvider);

    }

    /**
     * 设置默认请求头
     */
    private void setDefaultHeader() {
        if (this.getHeaderMap() == null) {
            headerMap = new HashMap<>();
        }
        if (!headerMap.containsKey("User-Agent")) {
            headerMap.put("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.74 Safari/537.36 Edg/99.0.1150.55");
        }
        if (!headerMap.containsKey("Content-Type")) {
            headerMap.put("Content-Type", contentType);
        }
    }

    /**
     * 将Map类型的Header构建为标准Header列表
     *
     * @return
     */
    private List<Header> buildHeader() {
        setDefaultHeader();
        return this.getHeaderMap()
                .entrySet().stream()
                .map(v -> new BasicHeader(v.getKey(), v.getValue()))
                .collect(Collectors.toList());
    }

    /**
     * 将参数列表转换为字符串拼接，以&符分隔
     *
     * @param list 参数列表
     * @return 转换后的字符串
     */
    public static String pairsToString(List<NameValuePair> list) {
        return list.stream().map(v -> v.getName() + "=" + v.getValue()).collect(Collectors.joining("&"));
    }

    /**
     * 构建Get请求
     *
     * @param uri 请求地址
     */
    public HttpGet buildGet(String uri) {
        return buildGet(uri, null);
    }

    /**
     * 构建Get请求
     *
     * @param uri 请求地址
     */
    public HttpGet buildGet(String uri, Map<String, String> params) {
        uri = formatURI(uri);
        if (this.getPairs() != null && this.getPairs().size() > 0) {
            uri = uri.contains("?") ? uri + "&" : uri + "?";
            uri = uri + pairsToString(this.getPairs());
        }
        return new HttpGet(uri);
    }

    /**
     * 构建Post请求，模拟Form表单提交
     *
     * @param uri 请求地址
     * @return Post请求
     */
    public HttpPost buildPost(String uri) {
        uri = formatURI(uri);
        HttpPost httpPost = new HttpPost(uri);

        httpPost.addHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
        //设置请求参数
        if (this.getPairs() != null && this.getPairs().size() > 0) {
            httpPost.setEntity(new UrlEncodedFormEntity(pairs, this.getReqCode()));
        }
        return httpPost;
    }

    /**
     * 格式化uri连接地址，补全协议
     *
     * @param uri 待格式化的连接地址
     * @return 格式化后的地址
     */
    private static String formatURI(String uri) {
        if (!uri.toLowerCase().startsWith("http://") && !uri.toLowerCase().startsWith("https://")) {
            return "http://" + uri;
        }
        return uri;
    }

    /**
     * Https证书管理
     *
     * @return 可识别证书集合
     */
    private static ConnectionSocketFactory trustHttpsCertificates() {
        TrustManager[] trustAllCertificates = new TrustManager[]{
                new X509TrustManager() {
                    @Override
                    public void checkClientTrusted(X509Certificate[] x509Certificates, String s) {
                    }

                    @Override
                    public void checkServerTrusted(X509Certificate[] x509Certificates, String s) {
                    }

                    @Override
                    public X509Certificate[] getAcceptedIssuers() {
                        return new X509Certificate[0];
                    }
                }
        };
        SSLContext sslContext = null;
        try {
            sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, trustAllCertificates, new SecureRandom());
        } catch (Exception e) {
            e.printStackTrace();
        }
        assert sslContext != null;
        return new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
    }

    public HttpClientBuilder getBuilder() {
        return builder;
    }

    public Map<String, String> getHeaderMap() {
        return headerMap;
    }

    /**
     * 设置请求头信息
     *
     * @param headerMap 请求头信息
     * @return 返回HttpConfig，用于链式调用
     */
    public HttpConfig setHeaderMap(Map<String, String> headerMap) {
        this.headerMap = headerMap;
        return this;
    }

    /**
     * 自定义添加请求头信息
     *
     * @param key   请求头键
     * @param value 请求头值
     * @return 返回HttpConfig，用于链式调用
     */
    public HttpConfig addHeader(String key, String value) {
        this.headerMap.put(key, value);
        return this;
    }

    public CookieStore getCookieStore() {
        return cookieStore;
    }

    /**
     * 设置CookieStore
     *
     * @param cookieStore cookiestore
     * @return 返回HttpConfig，用于链式调用
     */
    public HttpConfig setCookieStore(CookieStore cookieStore) {
        this.cookieStore = cookieStore;
        return this;
    }

    public BasicCredentialsProvider getBasicCredentialsProvider() {
        return basicCredentialsProvider;
    }

    /**
     * @param basicCredentialsProvider {@link BasicCredentialsProvider}
     * @return 返回HttpConfig，用于链式调用
     */
    public HttpConfig setBasicCredentialsProvider(BasicCredentialsProvider basicCredentialsProvider) {
        this.basicCredentialsProvider = basicCredentialsProvider;
        return this;
    }

    public List<NameValuePair> getPairs() {
        return pairs;
    }

    /**
     * 设置请求参数
     *
     * @param pairs 请求参数集合
     * @return 返回HttpConfig，用于链式调用
     */
    public HttpConfig setPairs(List<NameValuePair> pairs) {
        this.pairs = pairs;
        return this;
    }

    public Charset getReqCode() {
        return reqCode;
    }

    public HttpConfig setReqCode(Charset reqCode) {
        this.reqCode = reqCode;
        return this;
    }

    public Charset getResCode() {
        return resCode;
    }

    public HttpConfig setResCode(Charset resCode) {
        this.resCode = resCode;
        return this;
    }

    public HttpExplorer build() {
        return new HttpExplorer(this);
    }
}
